package com.gitee.wsl.security.hash.bcrypt

import com.fleeksoft.charset.Charsets
import com.gitee.wsl.base.Objects.equals
import com.gitee.wsl.base.Objects.hash
import com.gitee.wsl.ext.array.bytes.toCharArray
import com.gitee.wsl.ext.base.Arrays.arraycopy
import com.gitee.wsl.lang.bytearray.Bytes
import com.gitee.wsl.lang.bytearray.BytesTransformer
import com.gitee.wsl.mathematics.function.random.SecureRandom
import com.gitee.wsl.security.hash.bcrypt.BCrypt.MAJOR_VERSION
import com.gitee.wsl.security.hash.bcrypt.BCrypt.withDefaults
import kotlin.random.Random
import kotlin.test.*


class BcryptTestEntry(
    val plainPw: String, 
    val cost: Int, 
    val radix64Salt: String,
    val hash: String
) {
    override fun equals(o: Any?): Boolean {
        if (this === o) return true
        if (o == null || this::class != o::class) return false
        val that = o as BcryptTestEntry
        return cost == that.cost &&
                equals<String?>(plainPw, that.plainPw) &&
                equals<String?>(radix64Salt, that.radix64Salt) &&
                equals<String?>(hash, that.hash)
    }

    override fun hashCode(): Int {
        return hash(plainPw, cost, radix64Salt, hash)
    }

    companion object {
        fun testEntries(entries: Array<BcryptTestEntry>) {
            for (testEntry in entries) {
                val hashed = withDefaults().hash(
                    testEntry.cost,
                    Radix64Encoder.Default().decode(testEntry.radix64Salt.encodeToByteArray()),
                    testEntry.plainPw.encodeToByteArray()
                )

                assertContentEquals(
                    testEntry.hash.encodeToByteArray(), hashed,"hash does not match: \n\r" + testEntry.hash + " was \n\r" + hashed.decodeToString(),
                )
            }
        }
    }
}


class BcryptTest {
   
    private val testEntries: Array<BcryptTestEntry> =
        arrayOf<BcryptTestEntry>( // see: https://stackoverflow.com/a/12761326/774398
            BcryptTestEntry(
                "ππππππππ",
                10,
                ".TtQJ4Jr6isd4Hp.mVfZeu",
                "$2a$10$.TtQJ4Jr6isd4Hp.mVfZeuh6Gws4rOQ/vdBczhDx.19NFK0Y84Dle"
            ),  // see: http://openwall.info/wiki/john/sample-hashes
            BcryptTestEntry(
                "password",
                5,
                "bvIG6Nmid91Mu9RcmmWZfO",
                "$2a$05\$bvIG6Nmid91Mu9RcmmWZfO5HJIMCT8riNW0hEp8f6/FuA2/mHZFpe"
            ),  // see: http://cvsweb.openwall.com/cgi/cvsweb.cgi/Owl/packages/glibc/crypt_blowfish/wrapper.c?rev=HEAD
            BcryptTestEntry(
                "U*U",
                5,
                "CCCCCCCCCCCCCCCCCCCCC.",
                "$2a$05\$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"
            ),
            BcryptTestEntry(
                "U*U*",
                5,
                "CCCCCCCCCCCCCCCCCCCCC.",
                "$2a$05\$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK"
            ),
            BcryptTestEntry(
                "U*U*U",
                5,
                "XXXXXXXXXXXXXXXXXXXXXO",
                "$2a$05\$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a"
            ),
            BcryptTestEntry(
                "",
                6,
                "DCq7YPn5Rq63x1Lad4cll.",
                "$2a$06\$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s."
            ),  // see: https://bitbucket.org/vadim/bcrypt.net/src/464c41416dc9/BCrypt.Net.Test/TestBCrypt.cs?fileviewer=file-view-default
            BcryptTestEntry(
                "",
                8,
                "HqWuK6/Ng6sg9gQzbLrgb.",
                "$2a$08\$HqWuK6/Ng6sg9gQzbLrgb.Tl.ZHfXLhvt/SgVyWhQqgqcZ7ZuUtye"
            ),
            BcryptTestEntry(
                "",
                10,
                "k1wbIrmNyFAPwPVPSVa/ze",
                "$2a$10\$k1wbIrmNyFAPwPVPSVa/zecw2BCEnBwVS2GbrmgzxFUOqW9dk4TCW"
            ),
            BcryptTestEntry(
                "",
                12,
                "k42ZFHFWqBp3vWli.nIn8u",
                "$2a$12\$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO"
            ),
            BcryptTestEntry(
                "a",
                6,
                "m0CrhHm10qJ3lXRY.5zDGO",
                "$2a$06\$m0CrhHm10qJ3lXRY.5zDGO3rS2KdeeWLuGmsfGlMfOxih58VYVfxe"
            ),
            BcryptTestEntry(
                "a",
                8,
                "cfcvVd2aQ8CMvoMpP2EBfe",
                "$2a$08\$cfcvVd2aQ8CMvoMpP2EBfeodLEkkFJ9umNEfPD18.hUF62qqlC/V."
            ),
            BcryptTestEntry(
                "a",
                10,
                "k87L/MF28Q673VKh8/cPi.",
                "$2a$10\$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u"
            ),
            BcryptTestEntry(
                "a",
                12,
                "8NJH3LsPrANStV6XtBakCe",
                "$2a$12$8NJH3LsPrANStV6XtBakCez0cKHXVxmvxIlcz785vxAIZrihHZpeS"
            ),
            BcryptTestEntry(
                "abc",
                6,
                "If6bvum7DFjUnE9p2uDeDu",
                "$2a$06\$If6bvum7DFjUnE9p2uDeDu0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i"
            ),
            BcryptTestEntry(
                "abc",
                8,
                "Ro0CUfOqk6cXEKf3dyaM7O",
                "$2a$08\$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm"
            ),
            BcryptTestEntry(
                "abc",
                10,
                "WvvTPHKwdBJ3uk0Z37EMR.",
                "$2a$10\$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi"
            ),
            BcryptTestEntry(
                "abc",
                12,
                "EXRkfkdmXn2gzds2SSitu.",
                "$2a$12\$EXRkfkdmXn2gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q"
            ),
            BcryptTestEntry(
                "abcdefghijklmnopqrstuvwxyz",
                6,
                ".rCVZVOThsIa97pEDOxvGu",
                "$2a$06$.rCVZVOThsIa97pEDOxvGuRRgzG64bvtJ0938xuqzv18d3ZpQhstC"
            ),
            BcryptTestEntry(
                "abcdefghijklmnopqrstuvwxyz",
                8,
                "aTsUwsyowQuzRrDqFflhge",
                "$2a$08\$aTsUwsyowQuzRrDqFflhgekJ8d9/7Z3GV3UcgvzQW3J5zMyrTvlz."
            ),
            BcryptTestEntry(
                "abcdefghijklmnopqrstuvwxyz",
                10,
                "fVH8e28OQRj9tqiDXs1e1u",
                "$2a$10\$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq"
            ),
            BcryptTestEntry(
                "abcdefghijklmnopqrstuvwxyz",
                12,
                "D4G5f18o7aMMfwasBL7Gpu",
                "$2a$12\$D4G5f18o7aMMfwasBL7GpuQWuP3pkrZrOAnqP.bmezbMng.QwJ/pG"
            ),
            BcryptTestEntry(
                "~!@#$%^&*()      ~!@#$%^&*()PNBFRD",
                6,
                "fPIsBO8qRqkjj273rfaOI.",
                "$2a$06\$fPIsBO8qRqkjj273rfaOI.HtSV9jLDpTbZn782DC6/t7qT67P6FfO"
            ),
            BcryptTestEntry(
                "~!@#$%^&*()      ~!@#$%^&*()PNBFRD",
                8,
                "Eq2r4G/76Wv39MzSX262hu",
                "$2a$08\$Eq2r4G/76Wv39MzSX262huzPz612MZiYHVUJe/OcOql2jo4.9UxTW"
            ),
            BcryptTestEntry(
                "~!@#$%^&*()      ~!@#$%^&*()PNBFRD",
                10,
                "LgfYWkbzEvQ4JakH7rOvHe",
                "$2a$10\$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS"
            ),
            BcryptTestEntry(
                "~!@#$%^&*()      ~!@#$%^&*()PNBFRD",
                12,
                "WApznUOJfkEGSmYRfnkrPO",
                "$2a$12\$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC"
            )

        )

    @Test
    fun testEntriesAgainstRef() {
        BcryptTestEntry.testEntries(testEntries)
    }

    @Test
    fun quickStart() {
        val password = "1234"
        val bcryptHashString = BCrypt.withDefaults().hashToString(12, password.toCharArray())
        println(bcryptHashString)
        val result: BCrypt.Result = BCrypt.verifyer().verify(password.toCharArray(), bcryptHashString)
        assertTrue(result.verified)
    }

    @Test
    fun readmeExamples() {
        val password = "1234"
        //Versions
        val bcrypt2yChars = BCrypt.with(BCrypt.Version.VERSION_2Y).hashToChar(6, password.toCharArray())
        println(bcrypt2yChars)
        val bcrypt2bChars = BCrypt.with(BCrypt.Version.VERSION_2B).hashToChar(6, password.toCharArray())
        println(bcrypt2bChars)
        //byte[] vs char[]
        val bcryptHashBytes = BCrypt.withDefaults().hash(6, password.encodeToByteArray())
        val result =
            BCrypt.verifyer().verify(password.encodeToByteArray(), bcryptHashBytes)
        //verify strict
        val hash2y =
            BCrypt.with(BCrypt.Version.VERSION_2Y).hash(6, password.encodeToByteArray())
        val resultStrict: BCrypt.Result? =
            BCrypt.verifyer(BCrypt.Version.VERSION_2A).verifyStrict(password.encodeToByteArray(), hash2y)
        //overlong passwords
        BCrypt.with(LongPasswordStrategies.truncate(BCrypt.Version.VERSION_2Y)).hash(6, ByteArray(100))
        BCrypt.with(LongPasswordStrategies.hashSha512(BCrypt.Version.VERSION_2Y)).hash(6, ByteArray(100))
        //custom salt and secure random
        BCrypt.withDefaults().hash(6, Bytes.random(16).array(), password.encodeToByteArray())
        BCrypt.with(SecureRandom).hash(6, password.encodeToByteArray())
    }

    @Test
    fun testSimpleBcryptHashes() {
        val salt = byteArrayOf(
            0x5E,
            0xFA.toByte(),
            0xA7.toByte(),
            0xA3.toByte(),
            0xD9.toByte(),
            0xDF.toByte(),
            0x6E,
            0x7F.toByte(),
            0x8C.toByte(),
            0x78,
            0x96.toByte(),
            0xB1.toByte(),
            0x7B,
            0xA7.toByte(),
            0x6E,
            0x01
        )
        val bCrypt: BCrypt.Hasher = BCrypt.withDefaults()
        for (i in 4..9) {
            val hash: ByteArray = bCrypt.hash(i, salt, "abcdefghijkl1234567öäü-,:".encodeToByteArray())
            assertEquals(60, hash.size)
            println(hash.decodeToString())
        }
    }

    @Test
    @Throws(Exception::class)
    fun testHashAllVersions() {
        for (version in BCrypt.Version.SUPPORTED_VERSIONS) {
            checkHash(BCrypt.with(version))
        }
    }

    @Test
    @Throws(Exception::class)
    fun testSecureRandom() {
        checkHash(BCrypt.with(SecureRandom))
    }

    @Test
    @Throws(Exception::class)
    fun testLongPasswordStrategy() {
        checkHash(BCrypt.with(LongPasswordStrategy.TruncateStrategy(DEFAULT_VERSION.allowedMaxPwLength)))
    }

    @Test
    @Throws(Exception::class)
    fun testFullyCustom() {
        checkHash(
            BCrypt.with(
                BCrypt.Version.VERSION_2Y,
                LongPasswordStrategy.TruncateStrategy(BCrypt.Version.VERSION_2Y.allowedMaxPwLength)
            )
        )
    }

    @Throws(Exception::class)
    private fun checkHash(bCrypt: BCrypt.Hasher) {
        val verifyer = BCrypt.verifyer()

        val pw = "a90üdjanlasdn_asdlk"
        val salt = Bytes.random(16).array()
        val hash1 = bCrypt.hash(6, pw.toCharArray())
        val hash2= bCrypt.hash(7, salt, pw.encodeToByteArray())
        val hashData =
            bCrypt.hashRaw(7, salt, pw.encodeToByteArray())
        val hash3 = bCrypt.hashToChar(4, pw.toCharArray())
        val hash4 = bCrypt.hashToString(4, pw.toCharArray())

        assertFalse(Bytes.wrap(hash1).equals(hash2))
        assertTrue(
            verifyer.verify(
                pw.toCharArray(),
                hash1.toCharArray()
            ).verified
        )
        assertTrue(verifyer.verify(pw.encodeToByteArray(), hash2).verified)
        assertTrue(verifyer.verify(pw.toCharArray(), hash3).verified)
        assertTrue(verifyer.verify(pw.toCharArray(), hash4).verified)
        assertTrue(verifyer.verify(pw.toCharArray(), hash1).verified)
        assertTrue(verifyer.verify(pw.toCharArray(), hash2).verified)
        assertEquals(BCryptParser.Default(Radix64Encoder.Default()).parse(hash2), hashData)
    }

    @Test
   
    fun hashRandomByteArrays() {
        val pw = Bytes.random(Random.nextInt(68) + 2).array()
        val hash = BCrypt.withDefaults().hash(4, pw)
        assertTrue(BCrypt.verifyer().verify(pw, hash).verified)
        println(hash.decodeToString())
    }

    @Test
    fun testEmptyPw() {
        val hash = BCrypt.with(BCrypt.Version.VERSION_2A).hash(4, ByteArray(0))
        assertTrue(BCrypt.verifyer().verify(ByteArray(0), hash).verified)
        println(hash.decodeToString())
    }

//    @Test(expected = IllegalArgumentException::class)
//    fun createHashCostTooSmall() {
//        BCrypt.withDefaults().hash(3, "123".toCharArray())
//    }
//
//    @Test(expected = IllegalArgumentException::class)
//    fun createHashCostTooBig() {
//        BCrypt.withDefaults().hash(32, "123".toCharArray())
//    }
//
//    @Test(expected = IllegalArgumentException::class)
//    fun createHashWithNullSalt() {
//        BCrypt.withDefaults().hash(6, null, "123".getBytes())
//    }
//
//    @Test(expected = IllegalArgumentException::class)
//    fun createHashWithSaltTooShort() {
//        BCrypt.withDefaults().hash(6, ByteArray(15), "123".getBytes())
//    }
//
//    @Test(expected = IllegalArgumentException::class)
//    fun createHashWithSaltTooLong() {
//        BCrypt.withDefaults().hash(6, ByteArray(17), "123".getBytes())
//    }

//    @Test(expected = IllegalArgumentException::class)
//    fun createHashWithPwNull() {
//        BCrypt.withDefaults().hash(6, ByteArray(16), null)
//    }

    @Test//(expected = IllegalArgumentException::class)
    fun createHashWithPwEmptyNoNullTerm() {
        BCrypt.with(BCrypt.Version.VERSION_BC).hash(6, ByteArray(16), ByteArray(0))
    }

//    @Test(expected = IllegalArgumentException::class)
//    fun createHashWithCharPwNull() {
//        BCrypt.withDefaults().hash(6, null as CharArray?)
//    }

    @Test//(expected = IllegalArgumentException::class)
    fun createHashWithPwTooLong() {
        BCrypt.withDefaults().hash(
            6,
            ByteArray(16),
            ByteArray(DEFAULT_VERSION.allowedMaxPwLength + 1)
        )
    }

    @Test//(expected = IllegalArgumentException::class)
    fun createHashWithPwTooLong2() {
        BCrypt.withDefaults().hash(
            6,
            ByteArray(16),
            ByteArray(DEFAULT_VERSION.allowedMaxPwLength + 2)
        )
    }

    @Test
    fun testLongPassword() {
        val pw =
            Bytes.random(DEFAULT_VERSION.allowedMaxPwLength).array()
        val bcryptHashBytes = BCrypt.withDefaults().hash(4, pw)
        assertTrue(BCrypt.verifyer().verify(pw, bcryptHashBytes).verified)
    }

    @Test
    fun testLongTruncatedPassword() {
        val pw =
            Bytes.random(DEFAULT_VERSION.allowedMaxPwLength + 2).array()
        val salt = Bytes.random(16).array()
        val bcryptHashBytes1a =
            BCrypt.with(LongPasswordStrategies.truncate(DEFAULT_VERSION))
                .hash(4, salt, pw)
        val bcryptHashBytes1b =
            BCrypt.with(LongPasswordStrategies.truncate(DEFAULT_VERSION))
                .hash(
                    4,
                    salt,
                    Bytes.wrap(pw).resize(
                        DEFAULT_VERSION.allowedMaxPwLength + 1,
                        BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_ZERO_INDEX
                    ).array()
                )
        val bcryptHashBytes2 = BCrypt.withDefaults().hash(
            4,
            salt,
            Bytes.wrap(pw).resize(
                DEFAULT_VERSION.allowedMaxPwLength,
                BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_ZERO_INDEX
            ).array()
        )

        assertContentEquals(bcryptHashBytes1a, bcryptHashBytes1b)
        assertContentEquals(bcryptHashBytes1a, bcryptHashBytes2)
    }

    @Test
    fun testVariousPwLengthShouldBeDifferentHashes() {
        val pw: Bytes = Bytes.random(256)
        val salt = Bytes.random(16).array()

        val hashes = mutableSetOf<String>()
        for (i in 0..71) {
            val data = BCrypt.with(LongPasswordStrategies.truncate(DEFAULT_VERSION))
                    .hashRaw(
                        4,
                        salt,
                        pw.resize(i, BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_ZERO_INDEX).array()
                    )
            val hashHexString = data.rawHash.toHexString()
            assertFalse(hashes.contains(hashHexString),"hash already in set for length $i")
            hashes.add(hashHexString)
        }
    }

    @Test
    fun testLongHashedPassword() {
        val pw =
            Bytes.random(DEFAULT_VERSION.allowedMaxPwLength + 2).array()
        val salt = Bytes.random(16).array()
        val bcryptHashBytes1 =
            BCrypt.with(LongPasswordStrategies.hashSha512(DEFAULT_VERSION))
                .hash(4, salt, pw)
        val bcryptHashBytes2 =
            BCrypt.with(LongPasswordStrategies.hashSha512(DEFAULT_VERSION))
                .hash(
                    4,
                    salt,
                    Bytes.wrap(pw).resize(
                        DEFAULT_VERSION.allowedMaxPwLength + 1,
                        BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_ZERO_INDEX
                    ).array()
                )
        assertFalse(Bytes.wrap(bcryptHashBytes1).equals(bcryptHashBytes2))
    }

    @Test
    fun verifyWithResult() {
        val bCrypt: BCrypt.Hasher = BCrypt.withDefaults()
        val pw = "78PHasdhklöALÖö".encodeToByteArray()
        val hash = bCrypt.hash(8, Bytes.random(16).array(), pw)

        val result: BCrypt.Result = BCrypt.verifyer().verify(pw, hash)
        assertResult(result, true, BCrypt.Version.VERSION_2A, 8)
    }

//    @Test
//    fun verifyRawByteArrays() {
//        val bCrypt: BCrypt.Hasher = BCrypt.withDefaults()
//        val pw = Bytes.random(24).encodeRadix(36).getBytes()
//        val hash = bCrypt.hashRaw(6, Bytes.random(16).array(), pw)
//
//        val result: BCrypt.Result = BCrypt.verifyer().verify(pw, hash)
//        assertResult(result, true, BCrypt.Version.VERSION_2A, 6)
//    }

//    @Test
//    fun verifyRawByteArrays2() {
//        val bCrypt: BCrypt.Hasher = BCrypt.withDefaults()
//        val pw = Bytes.random(24).encodeBase36().getBytes()
//        val hash = bCrypt.hashRaw(7, Bytes.random(16).array(), pw)
//
//        val result: BCrypt.Result = BCrypt.verifyer().verify(pw, hash.cost, hash.rawSalt, hash.rawHash)
//        assertResult(result, true, BCrypt.Version.VERSION_2A, 7)
//    }

    @Test
    fun verifyWithResultChars() {
        val bCrypt: BCrypt.Hasher = BCrypt.withDefaults()
        val pw = "7OHIJAslkjdhö#d"
        val hash = bCrypt.hashToChar(6, pw.toCharArray())

        val result: BCrypt.Result = BCrypt.verifyer().verify(pw.toCharArray(), hash)
        assertResult(result, true, BCrypt.Version.VERSION_2A, 6)
    }

    @Test
    fun verifyIncorrectStrictVersion() {
        val bCrypt: BCrypt.Hasher = BCrypt.with(BCrypt.Version.VERSION_2Y)
        val pw = "78PHasdhklöALÖö".encodeToByteArray()
        val hash = bCrypt.hash(5, Bytes.random(16).array(), pw)

        val result: BCrypt.Result = BCrypt.verifyer(BCrypt.Version.VERSION_2A).verifyStrict(pw, hash)
        assertResult(result, false, BCrypt.Version.VERSION_2Y, 5)
    }

    @Test
    fun verifyIncorrectStrictVersionChars() {
        val bCrypt: BCrypt.Hasher = BCrypt.with(BCrypt.Version.VERSION_2X)
        val pw = "8PAsdjhlkjhkjla_ääas#d"
        val hash = bCrypt.hashToChar(5, pw.toCharArray())

        val result: BCrypt.Result = BCrypt.verifyer(BCrypt.Version.VERSION_2A).verifyStrict(pw.toCharArray(), hash)
        assertResult(result, false, BCrypt.Version.VERSION_2X, 5)
    }

    @Test
    fun verifyCorrectNonDefaultVersion() {
        val version= BCrypt.Version.VERSION_2X
        val cost = 4
        val bCrypt: BCrypt.Hasher = BCrypt.with(version)
        val pw = "8PAsdjhlkjhkjla_ääas#d"
        val hash1= bCrypt.hashRaw(cost, Bytes.random(16).array(), Bytes.from(pw).array())
        val hash2 = bCrypt.hashToChar(cost, pw.toCharArray())

        assertResult(BCrypt.verifyer(version).verify(pw.toCharArray(), hash2), true, version, cost)
        assertResult(BCrypt.verifyer(version).verifyStrict(pw.toCharArray(), hash2), true, version, cost)
        assertResult(BCrypt.verifyer(version).verify(Bytes.from(pw).array(), hash1), true, version, cost)
    }

    private fun assertResult(result: BCrypt.Result, verified: Boolean, version: BCrypt.Version?, cost: Int) {
        assertEquals(verified, result.verified)
        assertTrue(result.validFormat)
        assertEquals(version, result.details?.version)
        assertEquals(cost, result.details?.cost)
    }

    @Test
    fun verifyIncorrectFormat() {
        val result: BCrypt.Result = BCrypt.verifyer().verify(
            "78PHasdhklöALÖö".encodeToByteArray(),
            "$2a$6\$If6bvum7DFjUnE9p2uDeDu0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i".encodeToByteArray()
        )
        assertFalse(result.validFormat)
        assertFalse(result.verified)
        assertNotNull(result.formatErrorMessage)
        println(result.formatErrorMessage)
    }

    @Test
    fun testResultPojoMethods() {
        val results1: BCrypt.Result = BCrypt.Result(null, true)
        val results2: BCrypt.Result = BCrypt.Result(null, true)
        val results3: BCrypt.Result = BCrypt.Result(IllegalBCryptFormatException("test"))

        assertEquals(results1, results2)
        assertEquals(results1.hashCode(), results2.hashCode())
        assertNotEquals(results1, results3)
        assertNotEquals(results1.hashCode(), results3.hashCode())
        assertNotEquals(results2, results3)
        assertNotEquals(results2.hashCode(), results3.hashCode())

        assertNotNull(results1.toString())
        assertNotNull(results2.toString())
        assertNotNull(results3.toString())
    }

    @Test
    fun testHashDataPojoMethods() {
        val hd1 = BCrypt.HashData(6, BCrypt.Version.VERSION_2A, ByteArray(16), ByteArray(23))
        val hd2 = BCrypt.HashData(6, BCrypt.Version.VERSION_2A, ByteArray(16), ByteArray(23))
        val hd3 = BCrypt.HashData(7, BCrypt.Version.VERSION_2A, ByteArray(16), ByteArray(23))

        assertEquals(hd1, hd2)
        assertEquals(hd1.hashCode(), hd2.hashCode())
        assertNotEquals(hd1, hd3)
        assertNotEquals(hd1.hashCode(), hd3.hashCode())
        assertNotEquals(hd2, hd3)
        assertNotEquals(hd2.hashCode(), hd3.hashCode())

        assertNotNull(hd1.toString())
        assertNotNull(hd2.toString())
        assertNotNull(hd3.toString())
    }

    @Test
    fun testHashDataWipe() {
        val salt: Bytes = Bytes.random(16)
        val hash: Bytes = Bytes.random(23)
        val hashData = BCrypt.HashData(6, BCrypt.Version.VERSION_2A, salt.copy().array(), hash.copy().array())

        assertTrue(hash.equals(hashData.rawHash))
        assertTrue(salt.equals(hashData.rawSalt))

        hashData.wipe()

        assertFalse(hash.equals(hashData.rawHash))
        assertFalse(salt.equals(hashData.rawSalt))
    }

    @Test
    fun testVersionPojoMethods() {
        assertEquals(
            BCrypt.Version.VERSION_2A,
            BCrypt.Version(byteArrayOf(MAJOR_VERSION, 0x61), true, true, BCrypt.Version.MAX_PW_LENGTH_BYTE)
        )
        assertEquals(
            BCrypt.Version.VERSION_2Y,
            BCrypt.Version(byteArrayOf(MAJOR_VERSION, 0x79), true, true, BCrypt.Version.MAX_PW_LENGTH_BYTE)
        )
        assertEquals(
            BCrypt.Version.VERSION_2Y_NO_NULL_TERMINATOR,
            BCrypt.Version(byteArrayOf(MAJOR_VERSION, 0x79), true, false, BCrypt.Version.MAX_PW_LENGTH_BYTE)
        )
        assertNotEquals(BCrypt.Version.VERSION_2Y, BCrypt.Version.VERSION_2A)
        assertNotEquals(BCrypt.Version.VERSION_2A, BCrypt.Version.VERSION_2B)
        assertNotEquals(BCrypt.Version.VERSION_2X, BCrypt.Version.VERSION_2Y)

        assertEquals(BCrypt.Version.VERSION_2A.hashCode(), BCrypt.Version.VERSION_2A.hashCode())
        assertEquals(
            BCrypt.Version.VERSION_2A.hashCode(),
            BCrypt.Version(
                byteArrayOf(MAJOR_VERSION, 0x61),
                useOnly23bytesForHash = true,
                appendNullTerminator = true,
                allowedMaxPwLength = BCrypt.Version.MAX_PW_LENGTH_BYTE,
            ).hashCode()
        )

        assertNotEquals(BCrypt.Version.VERSION_2Y.hashCode(), BCrypt.Version.VERSION_2A.hashCode())
        assertNotEquals(BCrypt.Version.VERSION_2A.hashCode(), BCrypt.Version.VERSION_2B.hashCode())
        assertNotEquals(BCrypt.Version.VERSION_2X.hashCode(), BCrypt.Version.VERSION_2Y.hashCode())
    }

    @Test
    fun testVerifierWithLongPasswordStrategy() {
        val truncate = LongPasswordStrategies.truncate(BCrypt.Version.VERSION_2A)

        val pw = Bytes.random(200).array()
        val hash = BCrypt.with(truncate).hash(4, pw)

        assertTrue(BCrypt.verifyer(BCrypt.Version.VERSION_2A, truncate).verify(pw, hash).verified)
        assertTrue(BCrypt.verifyer(BCrypt.Version.VERSION_2A, LongPasswordStrategies.none()).verify(pw, hash).verified)

        // Strategies "truncate" and "none" are equivalent because key expansion will just ignore extra data.
        assertFalse(
            BCrypt.verifyer(
                BCrypt.Version.VERSION_2A,
                LongPasswordStrategies.hashSha512(BCrypt.Version.VERSION_2A)
            ).verify(pw, hash).verified
        )
        // The SHA-512 strategy yields a different hash
    }

    @Test
    fun testWithNullTerminatorWithinPw_shouldNotTerminate() {
        val pw1 = Bytes.from("secret").append(0x00).append("butthereismore").array()
        val pw2 = Bytes.from("secret").array()

        val salt = Bytes.random(16).array()

        val hash1: String? = Bytes.wrap(BCrypt.withDefaults().hash(4, salt, pw1)).toString()
        val hash2: String? = Bytes.wrap(BCrypt.withDefaults().hash(4, salt, pw2)).toString()

        assertNotEquals(hash1, hash2)
        println(hash1.toString() + "\n" + hash2)
    }
//
//    @Test
//    fun testVersionWithNullTerminator() {
//        val pw71 = "myverlongpasswordthatisatleast72charslongandlongnothisisnotlongenoughyo".toCharArray()
//        assertEquals(71, pw71.size)
//        assertEquals(71, Bytes.from(pw71).length())
//
//        // For a 71 characters password, with null terminator there is no repetition, whereas without null terminator
//        // the first character is used again as 72th byte.
//        val salt = Bytes.random(16).array()
//
//        val hash71_1 = BCrypt.with(
//            BCrypt.Version.VERSION_2Y_NO_NULL_TERMINATOR,
//            LongPasswordStrategies.truncate(BCrypt.Version.VERSION_2Y_NO_NULL_TERMINATOR)
//        ).hash(4, salt, Bytes.from(pw71).array())
//        val hash71_2 =
//            BCrypt.with(BCrypt.Version.VERSION_2Y, LongPasswordStrategies.truncate(BCrypt.Version.VERSION_2Y))
//                .hash(4, salt, Bytes.from(pw71).array())
//        assertNotEquals(Bytes.wrap(hash71_1).encodeUtf8(), Bytes.wrap(hash71_2).encodeUtf8())
//
//
//        val pw72 = "myverlongpasswordthatisatleast72charslongandlongnothisisnotlongenoughyom".toCharArray()
//        assertEquals(72, pw72.size)
//        assertEquals(72, Bytes.from(pw72).length())
//
//        val hash72_1 = BCrypt.with(
//            BCrypt.Version.VERSION_2Y_NO_NULL_TERMINATOR,
//            LongPasswordStrategies.truncate(BCrypt.Version.VERSION_2Y_NO_NULL_TERMINATOR)
//        ).hash(4, salt, Bytes.from(pw72).array())
//        val hash72_2 =
//            BCrypt.with(BCrypt.Version.VERSION_2Y, LongPasswordStrategies.truncate(BCrypt.Version.VERSION_2Y))
//                .hash(4, salt, Bytes.from(pw72).array())
//        assertEquals(Bytes.wrap(hash72_1).encodeUtf8(), Bytes.wrap(hash72_2).encodeUtf8())
//
//        assertEquals(Bytes.wrap(hash71_1).encodeUtf8(), Bytes.wrap(hash72_1).encodeUtf8())
//        println(Bytes.wrap(hash71_1).encodeUtf8() + "\n" + Bytes.wrap(hash71_2).encodeUtf8())
//    }

    @Test
    fun testReferenceValuesWithoutNullTerminator() {
        val pw = "myverlongpasswordthatisatleast72charslongandlongnothisisnotlongenoughyou".toCharArray()
        assertEquals(72, pw.size)

        val pw71 = CharArray(71)
        arraycopy(pw, 0, pw71, 0, 71)
        assertTrue(
            BCrypt.verifyer(BCrypt.Version.VERSION_2Y_NO_NULL_TERMINATOR)
                .verify(pw, "$2y$04\$d4CIUbwyucxm87BQnDWyI.xHDm2vyIZfBDOzjASNkn/yB.6lzLwOG".toCharArray()).verified
        )
        assertTrue(
            BCrypt.verifyer(BCrypt.Version.VERSION_2Y_NO_NULL_TERMINATOR)
                .verify(pw, "$2y$04\$w8S7HTjIfG.8RRVOhLZWtuH6eei2l7NZ/VhYUrDJndAjDmOqK6E0W".toCharArray()).verified
        )
        assertTrue(
            BCrypt.verifyer(
                BCrypt.Version.VERSION_2Y,
                LongPasswordStrategies.truncate(BCrypt.Version.VERSION_2Y)
            ).verify(pw, "$2y$04\$w8S7HTjIfG.8RRVOhLZWtuH6eei2l7NZ/VhYUrDJndAjDmOqK6E0W".toCharArray()).verified
        )
        assertTrue(
            BCrypt.verifyer(
                BCrypt.Version.VERSION_2Y,
                LongPasswordStrategies.truncate(BCrypt.Version.VERSION_2Y)
            ).verify(pw71, "$2y$04\$w8S7HTjIfG.8RRVOhLZWtu//55gj0VTX7XdNkQmDuPw.qQXsnvtkG".toCharArray()).verified
        )
    }

    @Test
    fun verifyInferVersion() {
        verifyInferedVersion(
            "<.S.2K(Zq'",
            "$2y$04\$VYAclAMpaXY/oqAo9yUpkuWmoYywaPzyhu56HxXpVltnBIfmO9tgu",
            BCrypt.Version.VERSION_2Y
        )
        verifyInferedVersion(
            "<.S.2K(Zq'",
            "$2x$04\$VYAclAMpaXY/oqAo9yUpkuWmoYywaPzyhu56HxXpVltnBIfmO9tgu",
            BCrypt.Version.VERSION_2X
        )
        verifyInferedVersion(
            "<.S.2K(Zq'",
            "$2a$04\$VYAclAMpaXY/oqAo9yUpkuWmoYywaPzyhu56HxXpVltnBIfmO9tgu",
            BCrypt.Version.VERSION_2A
        )
        verifyInferedVersion(
            "<.S.2K(Zq'",
            "$2b$04\$VYAclAMpaXY/oqAo9yUpkuWmoYywaPzyhu56HxXpVltnBIfmO9tgu",
            BCrypt.Version.VERSION_2B
        )
    }

    private fun verifyInferedVersion(pw: String, hash: String, expectedVersion: BCrypt.Version?) {
        val result: BCrypt.Result = BCrypt.verifyer().verify(pw.toCharArray(), hash.toCharArray())
        assertTrue(result.verified)
        assertEquals(expectedVersion, result.details?.version)
    }

//    @Test(expected = IllegalArgumentException::class)
//    fun verifyStrictWithoutVersionShouldThrow() {
//        BCrypt.verifyer().verifyStrict(
//            "<.S.2K(Zq'".toCharArray(),
//            "$2a$04\$VYAclAMpaXY/oqAo9yUpkuWmoYywaPzyhu56HxXpVltnBIfmO9tgu".toCharArray()
//        )
//    }

    companion object {
        val UTF_8 = Charsets.UTF8
        private val DEFAULT_VERSION: BCrypt.Version = BCrypt.Version.VERSION_2A
    }
}
